В данном задании вам необходимо самостоятельно реализовать один из алгоритмов кластеризации.
По аналогии с классами в scikit-learn, нужно реализовать класс, наследуемый от Base Estimator.
Подробнее про реализацию своих моделей в scikit-learn: here.
В классе помимо __init__() нужно реализовать два метода:
fit() - метод, выполняющий кластеризацию данных.predict() - метод, определяющий для нового объекта, к какому из кластеров он относится. Для удобства можно создавать дополнительные методы класса, которые будут вызываться в fit() или predict().
Функции для вычисления расстояний между объектами самим реализовывать не нужно, используйте реализации из scipy.
Алгоритм Dbscan.
Параметры:
scipy.spatial.distance, самописные функции (callable) и предрассчитанная матрица расстояний. Атрибуты:
Метод predict(): Для нового объекта вычисляется число основных точек из каждого кластера, попавших в окрестность $\varepsilon$. Объект определяется в кластер с наибольшим числом таких точек.
Note: Метод predict() не выполняется в случае, когда metric - это матрица расстояний.
import operator
import time
from itertools import combinations, repeat
import pandas as pd
import numpy as np
from scipy.spatial.distance import pdist, squareform, cdist
from sklearn.base import BaseEstimator, ClusterMixin
from sklearn.datasets import load_iris
class MyDBSCAN(BaseEstimator, ClusterMixin):
"""
MyDBSCAN is my realization of DBSCAN algorithm from sklearn.
It will return a list of cluster labels. The label -1 means noise, and then
the clusters are numbered starting from 1.
"""
def __init__(self, eps=0.5,
min_samples=5,
metric='euclidean',
metric_params=None):
'''
Parameters
----------
eps : float, default=0.5
Neighborhood radius 𝜀;
min_samples : int, default=5
minimum number of objects in the neighborhood 𝜀 for the main points;
metric : string, or callable, default='euclidean'
a function of the distance between objects;
metric_params : dict, default=None
Additional keyword arguments for the metric function;
'''
self.eps = eps
self.min_samples = min_samples
self.metric = metric
self.metric_params = metric_params if metric_params else {}
###
self.core_sample_coords_ = list()
self.labels_ = None
self.core_sample_indices_ = list()
###
self.X = None
self.size = 0
self.clusters = 0
def get_neighbours(self, dist_matrix: list) -> list:
"""
Find all neighbours of all points, if they exist.
:param dist_matrix: Pairwise distances between observations
in n-dimensional space.
:return: List of all neighbours.
"""
all_combs = list(combinations(range(self.size), 2))
all_neighbours = [[] * self.size for i in repeat(None, self.size)]
for i in range(0, len(all_combs)):
comb = list(all_combs[i])
dist = dist_matrix[i]
if dist <= self.eps:
all_neighbours[comb[1]].append(comb[0])
all_neighbours[comb[0]].append(comb[1])
return all_neighbours
def boost_cluster(self, point: int, point_neighbors: list, all_neighbours: list):
"""
Make a new cluster with label self.cluster from a particular point .\
:param point: The initial point for new cluster
:param point_neighbors: Neighbors of this point
:param all_neighbours: List of all neighbours for each point.
"""
self.labels_[point] = self.clusters
self.core_sample_coords_.append(self.X[point])
self.core_sample_indices_.append(point)
counter = 0
while counter < len(point_neighbors):
new_point = point_neighbors[counter]
# if this neighbor doesn't have enough points in its eps
# reachable point
if self.labels_[new_point] == -1:
self.labels_[new_point] = self.clusters
elif self.labels_[new_point] == 0:
self.labels_[new_point] = self.clusters
self.core_sample_coords_.append(self.X[new_point])
self.core_sample_indices_.append(point)
# get neighbors including 'core' point
nPoint_neighbors = [new_point] + all_neighbours[new_point]
if len(nPoint_neighbors) >= self.min_samples:
point_neighbors.extend(nPoint_neighbors)
counter += 1
def define_status(self, all_neighbours: list):
"""
Start to create clusters.
There are two values:
* -1 - noise point
* 0 - initially all labels are 0.
"""
self.labels_ = np.full(self.size, 0, dtype=np.int32)
for point in range(0, self.size):
if not (self.labels_[point] == 0):
continue
# get neighbors including 'core' point
point_neighbors = [point] + all_neighbours[point]
if len(point_neighbors) < self.min_samples:
self.labels_[point] = -1
else:
self.clusters += 1
self.boost_cluster(point, point_neighbors, all_neighbours)
return self.labels_
def fit(self, X):
"""
Perform DBSCAN clustering from features, or distance matrix.
:param X: {array-like, sparse matrix} of shape (n_samples, n_features), or \
(n_samples, n_samples)
Training instances to cluster, or distances between instances if
``metric='precomputed'``.
"""
if self.eps < 0.0:
raise ValueError("EPS must be positive!")
self.X = X
if self.metric != 'precomputed':
dist_matrix = pdist(X=X, metric=self.metric, **self.metric_params)
else:
dist_matrix = squareform(X)
self.size = X.shape[0]
all_neighbours = self.get_neighbours(dist_matrix)
return self.define_status(all_neighbours)
def predict(self, X):
'''
For the new object, the number of main points
from each cluster that fall in the neighborhood 𝜀 is calculated .
The object is defined in the cluster
with the largest number of such points.
:param X:
:return:
'''
if self.labels_ is None:
raise ValueError('Data must be fitted use "predict()".')
if self.metric == 'precomputed':
raise ValueError('The "predict()" method is not executed when metric is a distance matrix.')
n_samples = X.shape[0]
new_labels = np.full(n_samples, 0, dtype=np.int32)
dist_matrix = cdist(XA=X,
XB=self.core_sample_coords_,
metric=self.metric,
**self.metric_params)
clusters = list(np.unique(self.labels_))
for sample in range(0, n_samples):
clust_to_size = {clust: 0 for clust in clusters}
for point in range(0, len(self.core_sample_coords_)):
if dist_matrix[0][point] <= self.eps:
clust = self.labels_[self.core_sample_indices_[point]]
clust_to_size[clust] += 1
biggest_clust = max(clust_to_size.items(), key=operator.itemgetter(1))[0]
new_labels[sample] = biggest_clust
return new_labels
Вашу реализацию необходимо сравнить с питоновской реализацией алгоритма из sklearn или scipy. Результаты кластеризации должны совпадать.
Также необходимо сравнить скорость работы вашей реализации и питоновской (это нормально, если ваша реализация будет медленнее).
Сравнение необходимо выполнить на наборе данных iris.
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import seaborn as sns
iris = load_iris()
X = iris.data # использовать для кластеризации
y = iris.target # истинные метки цветков
iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_df.head()
new_obj = {'sepal length(cm)': [5.1],
'sepal width (cm)': [3.6],
'petal length (cm)': [1.4],
'petal width (cm)': [0.2]}
# NOISE EXAMPLE
# new_obj = {'sepal length(cm)': [100],
# 'sepal width (cm)': [100],
# 'petal length (cm)': [100],
# 'petal width (cm)': [100]}
df = pd.DataFrame(data=new_obj)
df
# Work of my DBSCAN
start_time = time.time()
dbscan_clustering = MyDBSCAN()
dbscan_clustering.fit(X)
print('MY TIME: ', time.time() - start_time)
print('MY LABELS: ', dbscan_clustering.labels_)
print('MY PREDICT: ', dbscan_clustering.predict(df))
# COMPARISON
from sklearn.cluster import DBSCAN
start_time = time.time()
cl_dbscan = DBSCAN(eps=0.5, min_samples=5, metric='euclidean')
cl_dbscan.fit(X)
print("ORIG TIME: ", time.time() - start_time)
print('ORIG LABELS:', cl_dbscan.labels_)
X = pd.DataFrame(X, columns=iris.feature_names)
X['class'] = [iris.target_names[i] for i in y]
X
sns.pairplot(X, hue='class', plot_kws={'alpha':0.5}, vars=iris.feature_names)
plt.show()
setosa=X.loc[X["class"]=="setosa"]
versicolor=X.loc[X["class"]=="versicolor"]
virginica=X.loc[X["class"]=="virginica"]
import plotly.graph_objects as go
#adding figure for Length/Width visualization
fig1=go.Figure()
#adding traces
fig1.add_trace(go.Scatter(x=setosa['sepal width (cm)'], y=setosa['sepal length (cm)'], name='Setosa',
mode='markers', marker_color='rgb(52, 152, 219)',
marker_symbol='star-triangle-up', marker_opacity=0.8,
hovertemplate="<b>SepalWidthCm:</b> %{x} <br><b>SepalLengthCm:</b> %{y}"))
fig1.add_trace(go.Scatter(x=versicolor['sepal width (cm)'], y=versicolor['sepal length (cm)'], name='Versicolor',
mode='markers', marker_color='rgb(170, 128, 255)',
marker_symbol='hexagram', marker_opacity=0.8,
hovertemplate="<b>SepalWidthCm:</b> %{x} <br><b>SepalLengthCm:</b> %{y}"))
fig1.add_trace(go.Scatter(x=virginica['sepal width (cm)'], y=virginica['sepal length (cm)'], name='Virginica',
mode='markers', marker_color='rgb(241, 196, 15)',
marker_symbol='star-diamond', marker_opacity=0.8,
hovertemplate="<b>SepalWidthCm:</b> %{x} <br><b>SepalLengthCm:</b> %{y}"))
#customizing figure
fig1.update_traces(mode='markers', marker_line_width=1.5, marker_size=12)
fig1.update_layout(template='plotly_white', xaxis=dict(title_text='SepalWidthCm', title_standoff=10),
yaxis=dict(title_text='SepalLengthCm', title_standoff=10),
title_text='Sepal Length/Width', title_x=0.5)
fig1.update_xaxes(showline=True, linewidth=3, linecolor='black',
showspikes=True, spikecolor='red', spikethickness=2)
fig1.update_yaxes(showline=True, linewidth=3, linecolor='black',
showspikes=True, spikecolor='red', spikethickness=2)
#showing figure
fig1.show()
Дополнительно вы можете поработать над эффективностью алгоритма по скорости и памяти, добавить поддержку многопоточности, или расширить базовый функционал.
pd.set_option('display.max_rows', 50)
Vars = pd.read_csv('Vars.txt', sep='\t')
Vars[Vars.iloc[:,2] == 2]
В данном задании вам предлагается проанализировать набор данных по различным городам США. Каждый город характеризуется следующими признаками:
pd.set_option('display.max_colwidth', None)
data_desc = pd.read_csv('Data_Description.txt', sep=':')
data_desc
Housing и Crime - наоборот.Population- статистический признак, не имеющий интерпретации как “лучше-хуже”.Place - уникальный идентификатор объекта (города), он не должен использоваться при кластеризации.Longitude и Latitude. Их также не следует использовать при кластеризации данных.data = pd.read_csv('Data.txt', sep=' ')
data
Place, Long и Lat. cmap = sns.cubehelix_palette(as_cmap=True)
data.plot.scatter(x="Long",
y="Lat",
figsize=(15, 10),
cmap=cmap)
plt.show()
# Выполните необходимую предобработку данных. Перед кластеризацией исключите из данных признаки Place, Long и Lat.
data_copy = data.copy(deep=True)
data_copy.drop(['Place', 'Long', 'Lat'], axis=1, inplace=True)
data_copy
data_copy.shape
data_copy.info()
missing_vals = data_copy.isna()
print('Missing values by features:', missing_vals.sum(), sep='\n')
for column in data_copy.columns:
print(f"{column}: unique data --> {data_copy[column].unique()}")
data_copy.describe()
Так как производится работа с расстояниями между объектами, разнородность в величинах признаков может сильно исказить результаты анализа. Так, признак, который сам по себе принимает высокие значений может быть определяющим при вычислении расстояний, в то время как признаки с низкими значениями могут не оказывать почти никакого влияния.
Чтобы решить эту проблему, перед анализом я выполняю СТАНДАРТИЗАЦИЮ данных.
from sklearn import preprocessing
# приведение в диапазон от 0 до 1
min_max_scaler = preprocessing.MinMaxScaler()
data_minmax = min_max_scaler.fit_transform(data_copy)
data_minmax = pd.DataFrame(data_minmax, columns=data_copy.columns)
data_minmax.head(10)
# Выполните кластеризацию иерархическим методом.
# Рассмотрите различные расстояния между объектами. Определите, какие следует использовать при кластеризации.
# Выполните кластеризацию с различными расстояниями между кластерами. Сравните результаты, сделайте выводы.
d = {'euclidean': pdist(data_minmax, 'euclidean'),
'cityblock': pdist(data_minmax, 'cityblock'),
'minkowski_6': pdist(data_minmax, 'minkowski', p=6),
'cosine': pdist(data_minmax, 'cosine'),
'chebyshev': pdist(data_minmax, 'chebyshev'),
'canberra': pdist(data_minmax, 'canberra')}
D = pd.DataFrame(d)
D.shape
D_corr = D.corr().loc[['cityblock', 'euclidean', 'minkowski_6', 'chebyshev', 'canberra', 'cosine'],
['cityblock', 'euclidean', 'minkowski_6', 'chebyshev', 'canberra', 'cosine']
]
D_corr
plt.figure(figsize=(12, 8))
sns.heatmap(D_corr, annot=True)
plt.show()
sns.pairplot(data_minmax,
plot_kws={'alpha':0.5}
)
plt.show()
from scipy.cluster.hierarchy import linkage, dendrogram
! Из матрицы корреляций видно, что расстояния Минковского, Чебышева и Манхеттенских кварталов лучше всего коррелируют с Евклидовым, поэтому в дальнейшем я буду рассматривать именно их.
def plot_elbow(Z, h=10, w=5):
plt.figure(figsize=(h, w))
plt.plot(np.array(range(1, np.shape(Z)[0]+1)),
Z[:,2][::-1],
marker='o')
plt.xlabel("Number of clusters")
plt.ylabel("Merge distance")
plt.show()
Z1 = linkage(data_minmax, method="complete", metric='euclidean')
plot_elbow(Z1, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z1
,p=40
,truncate_mode='lastp'
,color_threshold=1.5
)
plt.axhline(y=1.5, c='k')
plt.show()
from scipy.cluster.hierarchy import fcluster
fcluster(Z1, t = 3, criterion='maxclust')
Z2 = linkage(data_minmax, method="single", metric='euclidean')
plot_elbow(Z2, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z2
,p=25
,truncate_mode='lastp'
,color_threshold=0.5
)
plt.axhline(y=0.5, c='k')
plt.show()
Z3 = linkage(data_minmax, method="average", metric='euclidean')
plot_elbow(Z3, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z3
,p=20
,truncate_mode='lastp'
,color_threshold=1.0
)
plt.axhline(y=1.0, c='k')
plt.show()
fcluster(Z3, t = 3, criterion='maxclust')
Z4 = linkage(data_minmax, method="centroid", metric='euclidean')
plot_elbow(Z4, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z4
,p=10
,truncate_mode='lastp'
,color_threshold=1.2
)
plt.axhline(y=1.2, c='k')
plt.show()
fcluster(Z4, t = 2, criterion='maxclust')
Z5 = linkage(data_minmax, method="median", metric='euclidean')
plot_elbow(Z5, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z5
,p=10
,truncate_mode='lastp'
,color_threshold=1.1
)
plt.axhline(y=1.1, c='k')
plt.show()
fcluster(Z5, t = 2, criterion='maxclust')
Z6 = linkage(data_minmax, method="ward", metric='euclidean')
plot_elbow(Z6, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z6
,p=10
,truncate_mode='lastp'
,color_threshold=3
)
plt.axhline(y=3, c='k')
plt.show()
fcluster(Z6, t = 4, criterion='maxclust')
data_ew = data_normalized.copy()
data_ew['cluster'] = fcluster(Z6, t = 4, criterion='maxclust').astype(int)
data_ew
sns_plot = sns.pairplot(data_ew.sort_values('cluster'),
hue='cluster',
plot_kws={'alpha':0.5},
)
plt.show()
hc_complete = fcluster(linkage(data_minmax, method='complete'),
t=3
,criterion='maxclust'
)
hc_average = fcluster(linkage(data_minmax, method='average'),
t=3,
criterion='maxclust'
)
##
hc_centroid = fcluster(linkage(data_minmax, method='centroid'),
t=2,
criterion='maxclust'
)
hc_median = fcluster(linkage(data_minmax, method='median'),
t=2,
criterion='maxclust'
)
pd.crosstab(hc_average, hc_complete, rownames=['hc_average'], colnames=['hc_complete'])
pd.crosstab(hc_centroid, hc_median, rownames=['hc_centroid'], colnames=['hc_median'])
Z_mc = linkage(data_minmax, method="complete", metric='cityblock')
plot_elbow(Z_mc, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z_mc
,p=10
,truncate_mode='lastp'
,color_threshold=6
)
plt.axhline(y=4, c='k')
plt.show()
fcluster(Z_mc, t = 2, criterion='maxclust')
Z_ms = linkage(data_minmax, method="single", metric='cityblock')
plot_elbow(Z_ms, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z_ms
,p=10
,truncate_mode='lastp'
,color_threshold=1.7
)
plt.axhline(y=1.7, c='k')
plt.show()
Z_ma = linkage(data_minmax, method="average", metric='cityblock')
plot_elbow(Z_ma, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z_ma
,p=20
,truncate_mode='lastp'
,color_threshold=2.5
)
plt.axhline(y=2.5, c='k')
plt.show()
fcluster(Z_ma, t = 2, criterion='maxclust')
hc_complete = fcluster(linkage(data_minmax, method='complete'),
t=2
,criterion='maxclust'
)
hc_average = fcluster(linkage(data_minmax, method='average'),
t=2,
criterion='maxclust'
)
pd.crosstab(hc_average, hc_complete, rownames=['hc_average'], colnames=['hc_complete'])
Z_minkc = linkage(data_minmax, method="complete", metric='minkowski')
plot_elbow(Z_minkc, w = 9)
plt.figure(figsize=(15, 10))
dendrogram(Z_minkc
,p=25
,truncate_mode='lastp'
,color_threshold=1.5
)
plt.axhline(y=1.5, c='k')
plt.show()
fcluster(Z_minkc, t = 3, criterion='maxclust')
Z_minks = linkage(data_minmax, method="single", metric='minkowski')
plot_elbow(Z_minks, w = 9)
plt.figure(figsize=(15, 10))
dendrogram(Z_minks
,p=20
,truncate_mode='lastp'
,color_threshold=0.65
)
plt.axhline(y=0.65, c='k')
plt.show()
Z_minka = linkage(data_minmax, method="average", metric='minkowski')
plot_elbow(Z_minka, w = 9)
plt.figure(figsize=(15, 10))
dendrogram(Z_minka
,p=30
,truncate_mode='lastp'
,color_threshold=1.0
)
plt.axhline(y=1.0, c='k')
plt.show()
fcluster(Z_minka, t = 3, criterion='maxclust')
hc_complete = fcluster(linkage(data_minmax, method='complete'),
t=3
,criterion='maxclust'
)
hc_average = fcluster(linkage(data_minmax, method='average'),
t=3
,criterion='maxclust'
)
pd.crosstab(hc_average, hc_complete, rownames=['hc_average'], colnames=['hc_complete'])
Z_chc = linkage(data_minmax, method="complete", metric='chebyshev')
plot_elbow(Z_chc, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z_chc
,p=30
,truncate_mode='lastp'
,color_threshold=0.85
)
plt.axhline(y=0.85, c='k')
plt.show()
fcluster(Z_chc, t = 5, criterion='maxclust')
Z_chs = linkage(data_minmax, method="single", metric='chebyshev')
plot_elbow(Z_chs, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z_chs
,p=15
,truncate_mode='lastp'
,color_threshold=0.4
)
plt.axhline(y=0.4, c='k')
plt.show()
Z_cha = linkage(data_minmax, method="average", metric='chebyshev')
plot_elbow(Z_cha, w = 10)
plt.figure(figsize=(15, 10))
dendrogram(Z_cha
,p=30
,truncate_mode='lastp'
,color_threshold=0.8
)
plt.axhline(y=0.8, c='k')
plt.show()
# Выполните кластеризацию методом Dbscan. Используйте расстояния между объектами, определенные в предыдущем пункте.
# Реализуйте эвристику (см. лекции) для выбора параметров алгоритма. Подберите подходящие параметры алгоритма.
data_normalized = pd.DataFrame(preprocessing.normalize(data_copy, norm='l2', axis=0),
columns=data_copy.columns)
np.linalg.norm(data_normalized, axis=0)
from sklearn.neighbors import NearestNeighbors
neighbors = NearestNeighbors(n_neighbors=3)
neighbors_fit = neighbors.fit(data_normalized)
distances, indices = neighbors_fit.kneighbors(data_normalized)
distances = np.sort(distances, axis=0)
distances = distances[:,1]
plt.plot(distances)
from sklearn.cluster import DBSCAN
cl_dbscan_euc = DBSCAN(eps=0.05, min_samples=3, metric='euclidean')
cl_dbscan_euc.fit(data_normalized)
pd.Series(cl_dbscan_euc.labels_).value_counts()
dbscan_euc = data_copy.copy()
dbscan_euc['points'] = 'Reachable'
dbscan_euc.iloc[cl_dbscan_euc.core_sample_indices_, 10] = 'Core'
dbscan_euc.loc[cl_dbscan_euc.labels_ == -1, 'points'] = 'Outlier'
sns.pairplot(hue='points',
data=dbscan_euc,
height=10)
plt.show()
cl_dbscan_cb = DBSCAN(eps=0.11, min_samples=3, metric='cityblock')
cl_dbscan_cb.fit(data_normalized)
pd.Series(cl_dbscan_cb.labels_).value_counts()
dbscan_cb = data_copy.copy()
dbscan_cb['points'] = 'Reachable'
dbscan_cb.iloc[cl_dbscan_cb.core_sample_indices_, 10] = 'Core'
dbscan_cb.loc[cl_dbscan_cb.labels_ == -1, 'points'] = 'Outlier'
sns.pairplot(hue='points',
data=dbscan_cb,
height=10)
plt.show()
cl_dbscan_mink = DBSCAN(eps=0.08, min_samples=3, metric='minkowski', p=5)
cl_dbscan_mink.fit(data_normalized)
pd.Series(cl_dbscan_mink.labels_).value_counts()
cl_dbscan_cheb = DBSCAN(eps=0.09, min_samples=3, metric='chebyshev')
cl_dbscan_cheb.fit(data_normalized)
pd.Series(cl_dbscan_cheb.labels_).value_counts()
# Выполните кластеризацию методом kmeans.
# Определите наилучшее (на ваш взгляд) число кластеров.
from sklearn.cluster import KMeans
inertia = []
for k in range(1, 50):
Z = KMeans(n_clusters=k,
init = 'random',
n_init = 100,
max_iter = 1000).fit(data_normalized)
inertia.append(Z.inertia_)
plt.plot(range(1, 50), inertia, 'bo-', marker='s')
plt.xlabel('$k$')
plt.ylabel('Objective function value')
plt.axvline(x=4, c='r')
plt.show()
Z = KMeans(n_clusters = 4 # число кластеров
,init = 'random'
,n_init = 1
,max_iter = 100
,random_state=15434
,)
Z.fit(data_normalized)
pd.Series(Z.labels_).value_counts()
# координты центров кластеров
Z.cluster_centers_
# Значение целевой функции:
# сумма квадратов расстояний от объекта до центра кластера,
# к которому он принаджлежит
Z.inertia_
data_kmeans = data_normalized.copy()
data_kmeans['cluster'] = Z.labels_.astype(int)
data_kmeans
sns_plot = sns.pairplot(
data_kmeans.sort_values('cluster')
,hue='cluster'
,palette = 'tab10'
)
plt.show()
# сохраняю отдельно картинку
sns_plot.savefig("output_kmeans.png")
# (Бонусное) Выполните кластеризацию другими методами.
# Например, HDBSCAN или алгоритмы, реализованные в scikit-learn.
from sklearn.cluster import SpectralClustering
clustering = SpectralClustering(n_clusters=5,
assign_labels="discretize",
random_state=0).fit(data_normalized)
pd.Series(clustering.labels_).value_counts().sort_index()
data_sc = data_normalized.copy()
data_sc['cluster'] = clustering.labels_.astype(str)
data_sc
sns_plot = sns.pairplot(data_sc.sort_values('cluster'),
hue='cluster',
plot_kws={'alpha':0.5},
)
plt.show()
# сохраняю картинку
sns_plot.savefig("output_sc.png")
# В результате выполнения предыдущих пунктов вы должны получить 4
# или больше разбиений объектов (по одному на каждый метод).
# Сравните их между собой, сделайте выводы о сходствах и различиях.
# Оцените результаты каждой кластеризации, используя метрики, рассмотренные
# на занятиях (Silhouette и прочие).
Иерархическая кластеризация хуже подходит для кластеризации больших объемов данных в сравнении с методом k-средних. Это объясняется тем, что временная сложность алгоритма линейна для метода k-средних (O(n)) и квадратична для метода иерархической кластеризации (O(n2))
В кластеризации при помощи метода k-средних алгоритм начинает построение с произвольного выбора начальных точек, поэтому, результаты, генерируемые при многократном запуске алгоритма, могут отличаться.
pd.Series(cl_dbscan_cb.labels_).value_counts()
На данном наборе данных, такой метод кластеризации как DBSCAN, плохо справился с выделением кластерой структуры и основное кол-во точек попали в один кластер.
DBSCAN выделял 3-4 кластера, но по разбиению объектов, этот тип кластеризаци был также неэффективен.
euc_ward = fcluster(Z6, t = 4, criterion='maxclust')
euc_ward
pd.Series(Z.labels_).value_counts()
pd.Series(clustering.labels_).value_counts().sort_index()
На данном наборе данных, такие методы кластеризации как Иерархическая кластеризация (метод Варда), K-средних и Спектральная кластеризация, дали более интересные разбиения. Они лучше справились с разбиением данных на кластеры.
В этих кластеризациях я решила выделить 4 и 5 кластеров, так как по количеству объектов внутри одного кластера, данное кол-во мне показалось наиболее удачным.
Иерархическая кластеризация только при евклидовом расстоянии и методе ыарда показала хорошее разбиение на кластеры. (4 кластера)
K-средних - выделила 4 кластера, здесь выделился один кластер, который не особо удачен, там всего 4 элементов внутри, остальные распределены плюс/минус равномерно.
Спектральная кластеризация - выделила 5 кластеров, разбиение прошло на мой вгляд удачно.
Не предполагает знания истинных меток объектов, и позволяет оценить качество кластеризации, используя только саму (неразмеченную) выборку и результат кластеризации.
Силуэтом выборки называется средняя величина силуэта объектов данной выборки. Таким образом, силуэт показывает, насколько среднее расстояние до объектов своего кластера отличается от среднего расстояния до объектов других кластеров. Данная величина лежит в диапазоне [-1, 1]. Значения, близкие к -1, соответствуют плохим (разрозненным) кластеризациям, значения, близкие к нулю, говорят о том, что кластеры пересекаются и накладываются друг на друга, начения, близкие к 1, соответствуют "плотным" четко выделенным кластерам. Таким образом, чем больше силуэт, тем более четко выделены кластеры, и они представляют собой компактные, плотно сгруппированные облака точек.
С помощью силуэта можно выбирать оптимальное число кластеров (если оно заранее неизвестно) — выбирается число кластеров, максимизирующее значение силуэта.
Оценка определяется как средний показатель сходства каждого кластера с его наиболее похожим кластером, где сходство - это отношение расстояний внутри кластера к расстояниям между кластерами. Таким образом, кластеры, которые находятся дальше друг от друга и менее рассредоточены, дают лучший результат.
Минимальная оценка равна нулю, более низкие значения указывают на лучшую кластеризацию.
from sklearn import metrics
data_all_cl = data_normalized.copy()
data_all_cl['cl_kmeans'] = Z.labels_.astype(int)
data_all_cl['cl_dbscan'] = cl_dbscan_cb.labels_.astype(int)
data_all_cl['cl_spectral'] = clustering.labels_.astype(int)
data_all_cl
train1 = data_all_cl.drop(['cl_kmeans', 'cl_spectral', 'cl_dbscan'], axis=1)
cl_quality = []
cl_quality.append(({
'Silhouette': metrics.silhouette_score(train1, Z.labels_),
'Davies-Bouldin': metrics.davies_bouldin_score(train1, Z.labels_)}))
cl_quality.append(({
'Silhouette': metrics.silhouette_score(train1, cl_dbscan_cb.labels_),
'Davies-Bouldin': metrics.davies_bouldin_score(train1, cl_dbscan_cb.labels_)}))
cl_quality.append(({
'Silhouette': metrics.silhouette_score(train1, clustering.labels_),
'Davies-Bouldin': metrics.davies_bouldin_score(train1, clustering.labels_)}))
cl_quality.append(({
'Silhouette': metrics.silhouette_score(train1, fcluster(Z1, t = 3, criterion='maxclust')),
'Davies-Bouldin': metrics.davies_bouldin_score(train1, fcluster(Z6, t = 4, criterion='maxclust'))}))
results = pd.DataFrame(data=cl_quality, columns=['Silhouette', 'Davies-Bouldin'],
index=[ 'K-means', 'DBSCAN', 'Spectral', 'Hierarchical'])
results
# Выберите одно разбиение, наиболее подходящее на ваш взгляд.
# Предложите интерпретацию полученным кластерам или покажите,
# что этого сделать нельзя.
kmeans_clustering = Z
print('KMEANS clusters:')
pd.Series(kmeans_clustering.labels_).value_counts().sort_index()
ns_plot = sns.pairplot(data_kmeans.sort_values('cluster'),
hue='cluster',
plot_kws={'alpha':0.5},
palette = "tab10"
)
plt.show()
for col in data_copy.columns:
print(f'CORR WITW {col} :\n {data_copy.corr()[col].sort_values()}\n\n**********************\n')
sns.pairplot(x_vars="Climate",
y_vars="HousingCost",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Climate",
y_vars="Arts",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Climate",
y_vars="HlthCare",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Climate",
y_vars="Crime",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Climate",
y_vars="Recreat",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Educ",
y_vars="Transp",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Educ",
y_vars="HlthCare",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Educ",
y_vars="HousingCost",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Crime",
y_vars="HousingCost",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Crime",
y_vars="HlthCare",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Crime",
y_vars="Recreat",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Crime",
y_vars="Econ",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Econ",
y_vars="HousingCost",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="HousingCost",
y_vars="Recreat",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="HousingCost",
y_vars="Arts",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="HousingCost",
y_vars="HlthCare",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Recreat",
y_vars="HlthCare",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
sns.pairplot(x_vars="Recreat",
y_vars="Transp",
hue='cluster',
data=data_kmeans.sort_values('cluster'),
palette = "tab10",
height=10)
plt.show()
Логичее всего предположить, что:
# Оцените, как полученные кластеры распределены географически.
# Оцените, как полученные кластеры распределены по штатам.
# Можно ли выделить какую-то зависимость (территориальную или для штатов)?
# (Бонусное) Провизуализируйте распределение на карте США.
new = data.loc[:, ['Place', 'Lat', 'Long']]
new['cl_kmeans'] = data_all_cl.loc[:, ['cl_kmeans']]
new
import plotly.graph_objects as go
mapbox_access_token ="pk.eyJ1IjoiZG9rdGFwb2xhIiwiYSI6ImNrbmJ1NW5nNjExY2kyb21ybmdsN3FieW8ifQ.NB-vO0wD7bCK3W_c3KDHLA"
site_lat = new['Lat']
site_lon = new['Long']
locations_name = new['Place']
fig = go.Figure()
fig.add_trace(go.Scattermapbox(
lat=site_lat,
lon=site_lon,
mode='markers',
marker=go.scattermapbox.Marker(
size= 9,
color=new['cl_kmeans'],
colorscale='rainbow',
opacity=0.7
),
text="Place: " + data["Place"].astype(str) +
"<br>Population: " + data["Pop"].astype(str) +
"<br>Climate: " + data["Climate"].astype(str) +
"<br>HousingCost: " + data["HousingCost"].astype(str) +
"<br>HlthCare: " + data["HlthCare"].astype(str)+
"<br>Crime: " + data["Crime"].astype(str)+
"<br>Transp: " + data["Transp"].astype(str)+
"<br>Educ: " + data["Educ"].astype(str)+
"<br>Arts: " + data["Arts"].astype(str)+
"<br>Recreat: " + data["Recreat"].astype(str)+
"<br>Econ: " + data["Econ"].astype(str),
hoverinfo='text'
))
fig.update_layout(
title='KMeans Clustering (USA)',
autosize=False,
width=1050,
height=700,
hovermode='closest',
showlegend=False,
mapbox=dict(
accesstoken=mapbox_access_token,
bearing=0,
center=dict(
lat=38,
lon=-94
),
pitch=0,
zoom=3,
style='light',
),
)
fig.update_layout(mapbox_style="dark", mapbox_accesstoken=mapbox_access_token)
fig.show()